package jscinttools.scint;
/* jhplot libraries */
import jhplot.P0D;
import jhplot.P1D;
import jhplot.H1D;
import jhplot.H2D;
/* Aida libraries */
import hep.aida.ITupleFactory;
import hep.aida.ITuple;
import hep.aida.IFilter;
import hep.aida.IHistogramFactory;
import hep.aida.IHistogram1D;
import hep.aida.IHistogram2D;
import hep.aida.ICloud2D;
import hep.aida.ref.histogram.Cloud2D;
import hep.aida.ITree;
import hep.aida.IFilter;
import hep.aida.IEvaluator;
import hep.aida.ITupleColumn;
/* java libraries */
import java.lang.String;
import java.lang.StringBuilder;
import java.lang.System;
import java.lang.Math;
import java.lang.Double;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.File;
import java.awt.Color;
/* jscinttools libraries */
import jscinttools.utilities.aidautilities.AidaFactories;
import jscinttools.utilities.javautilities.MathUtil;
import jscinttools.utilities.javautilities.JavaUtil;

public class BiPoCounterFactory{
	
	private BiPoCounterFactory(){}
	
	public static BiPoCounterSNO createQueensBiPo(String pathAndName, int channel){
		return new QueensBiPoImp(pathAndName, channel);
	}
}

class QueensBiPoImp implements BiPoCounterSNO{
	private ITuple tuple;
	private String pathAndFile;
	private int channel;
	private final static ITupleFactory genTupFac = AidaFactories.TUPLE_FACTORY;
	private IFilter filter;
	private double[] timeCut = new double[2];
	private double[] timeRange = new double[2];
	private double[] intervalCut = new double[2];
	private double[] intervalRange = new double[2];
	private double[] betaCut = new double[2];
	private double[] betaRange = new double[2];
	private double[] alphaCut = new double[2];
	private double[] alphaRange = new double[2];
	private double[] tailCut = new double[2];
	private double[] tailRange = new double[2];
	private final static double[] INIT_VAL = {0.0, Double.POSITIVE_INFINITY};

	
	public QueensBiPoImp(String pathAndFile, int channel){
		this.pathAndFile = pathAndFile;
		this.channel = channel;
		String tupTitle = "tuple";
		this.tuple = genTupFac.create(tupTitle, tupTitle, tupColumns());
		readDataFile();
		this.timeCut = new double[]{tuple.columnMin(0), tuple.columnMax(0)};
		this.timeRange = new double[]{tuple.columnMin(0), tuple.columnMax(0)};
		this.intervalCut = new double[]{tuple.columnMin(1), tuple.columnMax(1)};
		this.intervalRange = new double[]{tuple.columnMin(1), tuple.columnMax(1)};
		this.betaCut = new double[]{tuple.columnMin(2), tuple.columnMax(2)};
		this.betaRange = new double[]{tuple.columnMin(2), tuple.columnMax(2)};
		this.alphaCut = new double[]{tuple.columnMin(3), tuple.columnMax(3)};
		this.alphaRange = new double[]{tuple.columnMin(3), tuple.columnMax(3)};
		this.tailCut = new double[]{tuple.columnMin(4), tuple.columnMax(4)};
		this.tailRange = new double[]{tuple.columnMin(4), tuple.columnMax(4)};
		this.filter = genTupFac.createFilter(filterExpr());
		this.filter.initialize(tuple);
	}
	
	private String tupColumns(){
		StringBuilder sb = new StringBuilder();
		sb.append("double time,");
		sb.append("double interval,");
		sb.append("double beta,");
		sb.append("double alpha,");
		sb.append("double tail");
		return sb.toString();
	}
	
	private String filterExpr(){
		StringBuilder sb = new StringBuilder();
		sb.append("(time>=" + timeCut[0] + ")&&");
		sb.append("(time<=" + timeCut[1] + ")&&");
		sb.append("(interval>=" + intervalCut[0] + ")&&");
		sb.append("(interval<=" + intervalCut[1] + ")&&");
		sb.append("(beta>=" + betaCut[0] + ")&&");
		sb.append("(beta<=" + betaCut[1] + ")&&");
		sb.append("(alpha>=" + alphaCut[0] + ")&&");
		sb.append("(alpha<=" + alphaCut[1] + ")&&");
		sb.append("(tail>=" + tailCut[0] + ")&&");
		sb.append("(tail<=" + tailCut[1] + ")");
		return sb.toString();
	}
	
	private void readDataFile(){
		try{
			InputStream ips = new FileInputStream(pathAndFile);
			InputStreamReader ipsr = new InputStreamReader(ips);
			BufferedReader br = new BufferedReader(ipsr);
			String line;
			System.out.println("\nReading File: " + pathAndFile);
			tuple.start();
			while ((line = br.readLine()) != null){
				if (line.startsWith(String.valueOf(channel)))
					fillTuple(line);
			}
			System.out.println("... Done");
			br.close(); 
		}		
		catch (Exception e){
			System.out.println(e.toString());
		}
	}
		
	private void fillTuple(String line){
		String[] val = line.split("\t");
		for (int i = 0; i < 5; i++)
			if (i == 1)
				tuple.fill(i, Double.parseDouble(val[i + 1]) / 1000.0);
			else
				tuple.fill(i, Double.parseDouble(val[i + 1]));
		tuple.addRow();
	}
	
	public int counts(){
		int cnt = 0;
		tuple.start();
		while (tuple.next()){
			if (filter.accept())
				cnt++;
		}
		return cnt;
	}
	
	public double totalTime(){
		tuple.setRow(tuple.rows() - 1);
		ITupleColumn.D col = (ITupleColumn.D) tuple.column("time");
		return col.value();
	}
	
	public ITuple toTuple(String path, String pathAndName, ITree tree){
		ITupleFactory tf = AidaFactories.ANALYSIS_FACTORY.createTupleFactory(tree);
		String[] pathName = pathAndNameSplitter(pathAndName);
		ITuple tup = tf.create(pathName[0], pathName[1], tupColumns());
		tuple.start();
		tup.start();
		while (tuple.next()){
			for (int i = 0; i < tuple.columns(); i++)
				tup.fill(i, tuple.getDouble(i));
			tup.addRow();
		}
		return tup;
	}
	
	public void alphaRange(double min, double max){
		this.alphaRange[0] = min;
		this.alphaRange[1] = max;
	}
	
	public void alphaCut(double min, double max){
		this.alphaCut[0] = min;
		this.alphaCut[1] = max;
		initFilter();
	}
	
	public H1D alpha(int bins){
		return genHisto(bins, "alpha");
	}
	
	public IHistogram1D alpha(int bins, String pathAndName, ITree tree){
		return genHisto(bins, pathAndName, tree, "alpha");
	}
	
	public void tailRange(double min, double max){
		this.tailRange[0] = min;
		this.tailRange[1] = max;
	}
	
	public void tailCut(double min, double max){
		this.tailCut[0] = min;
		this.tailCut[1] = max;
		initFilter();
	}
	
	public H1D tail(int bins){
		return genHisto(bins, "tail");
	}
	
	public IHistogram1D tail(int bins, String pathAndName, ITree tree){
		return genHisto(bins, pathAndName, tree, "tail");
	}
	
	public void betaRange(double min, double max){
		this.betaRange[0] = min;
		this.betaRange[1] = max;
	}
	
	public void betaCut(double min, double max){
		this.betaCut[0] = min;
		this.betaCut[1] = max;
		initFilter();
	}
	
	public H1D beta(int bins){
		return genHisto(bins, "beta");
	}
	
	public IHistogram1D beta(int bins, String pathAndName, ITree tree){
		return genHisto(bins, pathAndName, tree, "beta");
	}
	
	public void timeRange(double min, double max){
		this.timeRange[0] = min;
		this.timeRange[1] = max;
	}
	
	public void timeCut(double min, double max){
		this.timeCut[0] = min;
		this.timeCut[1] = max;
		initFilter();
	}
	
	public H1D time(int bins){
		return genHisto(bins, "time");
	}
	
	public H1D time(double[] binEges){
		return genHisto(binEges, "time");
	}
	
	public IHistogram1D time(int bins, String pathAndName, ITree tree){
		return genHisto(bins, pathAndName, tree, "time");
	}
	
	public IHistogram1D time(double[] binEges, String pathAndName, ITree tree){
		return genHisto(binEges, pathAndName, tree, "time");
	}
	
	public void intervalRange(double min, double max){
		this.intervalRange[0] = min;
		this.intervalRange[1] = max;
	}
	
	public void intervalCut(double min, double max){
		this.intervalCut[0] = min;
		this.intervalCut[1] = max;
		initFilter();
	}
	
	public H1D interval(int bins){
		return genHisto(bins, "interval");
	}
	
	public H1D interval(double[] binEdges){
		return genHisto(binEdges, "interval");
	}
	
	public IHistogram1D interval(int bins, String pathAndName, ITree tree){
		return genHisto(bins, pathAndName, tree, "interval");
	}
	
	public IHistogram1D interval(double[] binEdges, String pathAndName, ITree tree){
		return genHisto(binEdges, pathAndName, tree, "interval");
	}
	
	public H2D versus(String nameX, int binsX, String nameY, int binsY){
		return genHisto(nameX, binsX, nameY, binsY);
	}
	
	public IHistogram2D versus(String nameX, int binsX, String nameY, int binsY, String pathAndName, ITree tree){
		return genHisto(nameX, binsX, nameY, binsY, pathAndName, tree);
	}
	
	public P1D versus(String nameX, String nameY){
		return genCloud(nameX, nameY);
	}
	
	public ICloud2D versus(String nameX, String nameY, String pathAndName, ITree tree){
		return genCloud(nameX, nameY, pathAndName, tree);
	}
	
	private IHistogram1D genHisto(int bins, String pathAndName, ITree tree, String evalExpr){
		IHistogramFactory hf = AidaFactories.ANALYSIS_FACTORY.createHistogramFactory(tree);
		double[] range = range(evalExpr);
		IHistogram1D histo = hf.createHistogram1D(pathAndName, bins, range[0], range[1]);
		IEvaluator eval = genTupFac.createEvaluator(evalExpr);
		initFilter();
		tuple.project(histo, eval, this.filter);
		return histo;	
	}
	
	private IHistogram1D genHisto(double[] binEdges, String pathAndName, ITree tree, String evalExpr){
		IHistogramFactory hf = AidaFactories.ANALYSIS_FACTORY.createHistogramFactory(tree);
		String[] pathName = pathAndNameSplitter(pathAndName);
		IHistogram1D histo = hf.createHistogram1D(pathName[0], pathName[1], binEdges);
		IEvaluator eval = genTupFac.createEvaluator(evalExpr);
		initFilter();
		tuple.start();
		while(tuple.next()){
			ITupleColumn.D col = (ITupleColumn.D) tuple.column(evalExpr);
			double colVal = col.value();
			double scalFac = 1.0 / JavaUtil.intervalWidth(colVal, binEdges);
			if (filter.accept())
				histo.fill(colVal, scalFac);
		}
		return histo;	
	}
	
	private H1D genHisto(int bins, String evalExpr){
		H1D h1d = new H1D(genHisto(bins, " ", AidaFactories.TREE, evalExpr));
		h1d.setTitle(evalExpr);
		h1d.setColor(Color.BLUE);
		h1d.setStyle("h");
		h1d.setPenWidth(2);
		h1d.setErrAll(false);
		return h1d;
	}
	
	private H1D genHisto(double[] binEdges, String evalExpr){
		H1D h1d = new H1D(genHisto(binEdges, " ", AidaFactories.TREE, evalExpr));
		h1d.setTitle(evalExpr);
		h1d.setColor(Color.BLUE);
		h1d.setStyle("h");
		h1d.setPenWidth(2);
		h1d.setErrAll(false);
		return h1d;
	}
	
	private IHistogram2D genHisto(String evalExprX, int binsX, String evalExprY, int binsY, String pathAndName, ITree tree){
		IHistogramFactory hf = AidaFactories.ANALYSIS_FACTORY.createHistogramFactory(tree);
		double[] rangeX = range(evalExprX);
		double[] rangeY = range(evalExprY);
		IHistogram2D histo = hf.createHistogram2D(pathAndName, binsX, rangeX[0], rangeX[1], binsY, rangeY[0], rangeY[1]);
		IEvaluator evalX = genTupFac.createEvaluator(evalExprX);
		IEvaluator evalY = genTupFac.createEvaluator(evalExprY);
		initFilter();
		tuple.project(histo, evalX, evalY, this.filter);
		return histo;	
	}
	
	private H2D genHisto(String evalExprX, int binsX, String evalExprY, int binsY){
		H2D h2d = new H2D(genHisto(evalExprX, binsX, evalExprY, binsY, " ", AidaFactories.TREE));
		h2d.setTitle(evalExprX + " Vs " + evalExprY);
		return h2d;
	}
	
	private ICloud2D genCloud(String evalExprX, String evalExprY, String pathAndName, ITree tree){
		IHistogramFactory hf = AidaFactories.ANALYSIS_FACTORY.createHistogramFactory(tree);
		double[] rangeX = range(evalExprX);
		double[] rangeY = range(evalExprY);
		String[] pathName = pathAndNameSplitter(pathAndName);
		ICloud2D cloud = hf.createCloud2D(pathName[0], pathName[1], -1);
		IEvaluator evalX = genTupFac.createEvaluator(evalExprX);
		IEvaluator evalY = genTupFac.createEvaluator(evalExprY);
		initFilter();
		tuple.project(cloud, evalX, evalY, this.filter);
		return cloud;	
	}
	
	private P1D genCloud(String evalExprX, String evalExprY){
		P1D p1d = new P1D((Cloud2D) genCloud(evalExprX, evalExprY, " ", AidaFactories.TREE));
		p1d.setSymbol(11);
		p1d.setErrAll(false);
		p1d.setSymbolSize(1);
		p1d.setColor(Color.BLUE);
		p1d.setTitle(evalExprX + " Vs " + evalExprY);
		return p1d;
	}
	
	private double[] range(String evalExpr){
		if (evalExpr.equals("alpha"))
			return this.alphaRange;
		else if (evalExpr.equals("tail"))
			return this.tailRange;
		else if (evalExpr.equals("beta"))
			return this.betaRange;
		else if (evalExpr.equals("time"))
			return this.timeRange;
		else if (evalExpr.equals("interval"))
			return this.intervalRange;
		else
			return evalRange(evalExpr);
	}
	
	private double[] evalRange(String evalExpr){
		IEvaluator eval = genTupFac.createEvaluator(evalExpr);
		initFilter();
		double max = tuple.evaluateMax(eval);
		System.out.println(max);
		if (max == Double.POSITIVE_INFINITY)
			max = 1.0;
		return new double[]{0.0, max};
	}
	
	private String[] pathAndNameSplitter(String pathAndName){
		boolean sep = pathAndName.contains(File.separator);
		String[] sepPathName = new String[2];
		if (sep)
			return sepPathName = splitPathName(pathAndName);
		else
			return sepPathName = noSplitPathName(pathAndName);
	}
	
	private String[] splitPathName(String pathAndName){
		String[] sepPathName = new String[2];
		String[] allPart = pathAndName.split(File.separator);
		StringBuilder sb = new StringBuilder();
		int partLength = allPart.length;
		for (int i=0; i<partLength - 1; i++){
			sb.append(allPart[i]);
			if (i<partLength - 2)
				sb.append(File.separator);
		}
		sepPathName[0] = sb.toString();
		sepPathName[1] = allPart[partLength - 1];
		return sepPathName;
	}
	
	private String[] noSplitPathName(String pathAndName){
		return new String[]{" ", pathAndName};
	}
	
	private void initFilter(){
		this.filter = genTupFac.createFilter(filterExpr());
		this.filter.initialize(tuple);
	}
}
